/*
 * vendor/amlogic/media/common/codec_mm/codec_mm_keeper.c
 *
 * Copyright (C) 2016 Amlogic, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/genalloc.h>
#include <linux/amlogic/media/codec_mm/codec_mm.h>
#include <linux/amlogic/media/codec_mm/codec_mm_scatter.h>
#include <linux/amlogic/media/codec_mm/codec_mm_keeper.h>

#include <linux/workqueue.h>
#include "codec_mm_priv.h"
#include "codec_mm_scatter_priv.h"
#define KEEP_NAME "keeper"
#define MAX_KEEP_FRAME 16
#define START_KEEP_ID 0x9
#define MAX_KEEP_ID (INT_MAX - 1)

struct keep_mem_info {
    void *handle;
    int keep_id;
    int type;
    int user;
    u64 delay_on_jiffies64;
};

struct codec_mm_keeper_mgr {
    int num;
    spinlock_t lock;
    struct delayed_work dealy_work;
    int work_runs;
    struct keep_mem_info keep_list[MAX_KEEP_FRAME];
    int next_id; /* id for keep & free. */
};

static struct codec_mm_keeper_mgr codec_keeper_mgr_private;
static struct codec_mm_keeper_mgr *get_codec_mm_keeper_mgr(void)
{
    return &codec_keeper_mgr_private;
}

int is_codec_mm_keeped(void *mem_handle)
{
    return codec_mm_has_owner(mem_handle, KEEP_NAME);
}
EXPORT_SYMBOL(is_codec_mm_keeped);
/*
 * not call in interrupt;
 */
int codec_mm_keeper_mask_keep_mem(void *mem_handle, int type)
{
    struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
    int keep_id = -1;
    int i;
    unsigned long flags;
    int ret;
    int slot_i = -1;
    int have_samed = 0;

    if (codec_mm_get_keep_debug_mode() & 1) {
        pr_err("codec_mm_keeper_mask_keep_mem %p\n", mem_handle);
    }
    if (!mem_handle) {
        pr_err("NULL mem_handle for keeper!!\n");
        return -2L;
    }
    if (type != MEM_TYPE_CODEC_MM_SCATTER && type != MEM_TYPE_CODEC_MM) {
        pr_err("not valid type for keeper!!,%d\n", type);
        return -3L;
    }
    if (type == MEM_TYPE_CODEC_MM_SCATTER) {
        ret = codec_mm_scatter_inc_for_keeper(mem_handle);
        if (ret < 0) {
            pr_err("keeper scatter failed,%d,handle:%p\n", ret, mem_handle);
            if (codec_mm_get_keep_debug_mode() & 1) {
                codec_mm_dump_all_scatters();
            }
            return -4L;
        }
    } else if (type == MEM_TYPE_CODEC_MM) {
        ret = codec_mm_request_shared_mem(mem_handle, KEEP_NAME);
        if (ret < 0) {
            pr_err("keeper codec mm failed,%d,handle:%p\n", ret, mem_handle);
            return -4L;
        }
    }
    spin_lock_irqsave(&mgr->lock, flags);
    keep_id = mgr->next_id++;
    if (mgr->next_id >= MAX_KEEP_ID)
        mgr->next_id = START_KEEP_ID;
    for (i = 0; i < MAX_KEEP_FRAME; i++) {
        if (!mgr->keep_list[i].handle && slot_i < 0) {
            slot_i = i;
        } else if (mgr->keep_list[i].handle == mem_handle) {
            have_samed = 1;
            keep_id = mgr->keep_list[i].keep_id;
            break;
        }
    }
    if (slot_i >= 0 && !have_samed) {
        mgr->keep_list[slot_i].handle = mem_handle;
        mgr->keep_list[slot_i].keep_id = keep_id;
        mgr->keep_list[slot_i].type = type;
        mgr->keep_list[slot_i].user = 1;
        mgr->num++;
    } else {
        if (!have_samed) {
            keep_id = -1;
        }
    }
    if (codec_mm_get_keep_debug_mode() & 1) {
        /* keeped info */
        pr_err("codec_mm_keeper_mask_keep_mem %p id=%d\n", mem_handle, keep_id);
    }
    if (keep_id < 0 || have_samed) {
        if (type == MEM_TYPE_CODEC_MM_SCATTER) {
            ret = codec_mm_scatter_dec_keeper_user(mem_handle, 0);
        } else if (type == MEM_TYPE_CODEC_MM) {
            codec_mm_release_with_check(mem_handle, KEEP_NAME);
        }
        if (keep_id < 0) {
            pr_err("keep mem failed because keep buffer fulled!!!\n");
        }
    }
    spin_unlock_irqrestore(&mgr->lock, flags);

    return keep_id;
}
EXPORT_SYMBOL(codec_mm_keeper_mask_keep_mem);

/*
 * can call in irq
 */
int codec_mm_keeper_unmask_keeper(int keep_id, int delayms)
{
    struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
    int i;
    unsigned long flags;

    if (codec_mm_get_keep_debug_mode() & 1) {
        pr_err("codec_mm_keeper_unmask_keeper %d\n", keep_id);
    }
    if (keep_id < START_KEEP_ID || keep_id >= MAX_KEEP_ID) {
        pr_err("invalid keepid %d\n", keep_id);
        return -1;
    }
    spin_lock_irqsave(&mgr->lock, flags);
    for (i = 0; i < MAX_KEEP_FRAME; i++) {
        if (keep_id == mgr->keep_list[i].keep_id) {
            mgr->keep_list[i].user--; /* mask can free. */
            mgr->keep_list[i].delay_on_jiffies64 = get_jiffies_64() + delayms * HZ / 1000L;
            break;
        }
    }
    spin_unlock_irqrestore(&mgr->lock, flags);
    schedule_delayed_work(&mgr->dealy_work, delayms); /* do free later, */
    return 0;
}
EXPORT_SYMBOL(codec_mm_keeper_unmask_keeper);

static int codec_mm_keeper_free_keep(int index)
{
    struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
    void *mem_handle = NULL;
    int type;
    unsigned long flags;

    spin_lock_irqsave(&mgr->lock, flags);
    mem_handle = mgr->keep_list[index].handle;
    type = mgr->keep_list[index].type;
    mgr->keep_list[index].handle = NULL;
    mgr->keep_list[index].delay_on_jiffies64 = 0;
    mgr->num--;
    spin_unlock_irqrestore(&mgr->lock, flags);
    if (!mem_handle) {
        return -1;
    }
    if (type == MEM_TYPE_CODEC_MM) {
        codec_mm_release_with_check(mem_handle, KEEP_NAME);
    } else if (type == MEM_TYPE_CODEC_MM_SCATTER) {
        struct codec_mm_scatter *sc = mem_handle;

        codec_mm_scatter_dec_keeper_user(sc, 0);
    }

    return 0;
}

/*
 * can't call it
 * in irq/timer.tasklet
 */
int codec_mm_keeper_free_all_keep(int force)
{
    struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
    int i;
    int time_after, want_free;
    for (i = 0; i < MAX_KEEP_FRAME; i++) {
        struct keep_mem_info *info = &mgr->keep_list[i];
        if (!info->handle || info->keep_id < 0) {
            continue;
        }
        want_free = 0;
        time_after = time_after64(get_jiffies_64(), info->delay_on_jiffies64);
        if (force == 1) {
            want_free = 1;
        } else if (force == 2L && info->user <= 0) {
            want_free = 1;
        } else if (info->user <= 0 && time_after) {
            want_free = 1;
        }

        if (want_free) {
            codec_mm_keeper_free_keep(i);
        }
    }

    return 0;
}

int codec_mm_keeper_dump_info(void *buf, int size)
{
    struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
    char *pbuf = buf;
    char sbuf[512];
    int tsize = 0;
    int s;
    int i;

    if (!pbuf) {
        pbuf = sbuf;
    }
#define BUFPRINT(args...)                                                                                              \
    do {                                                                                                               \
        s = sprintf(pbuf, args);                                                                                       \
        tsize += s;                                                                                                    \
        pbuf += s;                                                                                                     \
    } while (0)

    BUFPRINT("dump keep list:next_id=%d,work_run:%d,num=%d\n", mgr->next_id, mgr->work_runs, mgr->num);
    for (i = 0; i < MAX_KEEP_FRAME; i++) {
        BUFPRINT("keeper:[%d]:\t\tid:%d\thandle:%p,type:%d,user:%d\n", i, mgr->keep_list[i].keep_id,
                 mgr->keep_list[i].handle, mgr->keep_list[i].type, mgr->keep_list[i].user);
    }
#undef BUFPRINT
    if (!buf) {
        pr_info("%s", sbuf);
    }
    return tsize;
}

static void codec_mm_keeper_monitor(struct work_struct *work)
{
    struct codec_mm_keeper_mgr *mgr = container_of(work, struct codec_mm_keeper_mgr, dealy_work.work);
    mgr->work_runs++;
    codec_mm_keeper_free_all_keep(0);
    if (mgr->num > 0) { /* have some not free, run later. */
        schedule_delayed_work(&mgr->dealy_work, 10L);
    }
}

int codec_mm_keeper_mgr_init(void)
{
    struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();

    memset(mgr, 0, sizeof(struct codec_mm_keeper_mgr));
    mgr->next_id = START_KEEP_ID;
    spin_lock_init(&mgr->lock);
    INIT_DELAYED_WORK(&mgr->dealy_work, codec_mm_keeper_monitor);
    return 0;
}
